home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1998 September
/
Macworld (1998-09).dmg
/
Shareware World
/
Info
/
For Developers
/
MacZoop 1.8.3
/
More Classes
/
File Classes
/
ZBlockFile.cpp
< prev
next >
Wrap
Text File
|
1997-12-24
|
13KB
|
578 lines
/*************************************************************************************************
*
*
* ObjectMacZapp -- a standard Mac OOP application template
*
*
*
* ZBlockFile.cpp -- a generic file object that includes some basic block
* management facilities. This type of file is useful for
* implementing VM schemes and databases, etc.
*
*
* © 1996, Graham Cox
*
*
*
*
*************************************************************************************************/
#include "ZBlockFile.h"
#include "ZArray.h"
#include "ZErrors.h"
#include "ZDefines.h"
ZBlockFile::ZBlockFile( const FSSpec& aFileSpec )
: ZFile( aFileSpec )
{
bmap = NULL;
refSeed = 1;
useCount = 0;
}
ZBlockFile::ZBlockFile( Str255 fName )
: ZFile( fName )
{
bmap = NULL;
refSeed = 1;
useCount = 0;
}
ZBlockFile::~ZBlockFile()
{
if ( bmap )
ForgetObject( bmap );
}
/*------------------------------------*** OPEN ***------------------------------------*/
/*
open the file and initialise the map, from file if this is overridden to do so.
----------------------------------------------------------------------------------------*/
void ZBlockFile::Open()
{
ZFile::Open();
InitMap();
ReadMap();
}
/*-------------------------------*** GETBLOCKCOUNT ***--------------------------------*/
/*
return the total number of blocks
----------------------------------------------------------------------------------------*/
long ZBlockFile::GetBlockCount()
{
if ( bmap )
return bmap->CountItems();
else
return 0;
}
/*--------------------------------*** GETBLOCKREF ***---------------------------------*/
/*
get the ref for the indexed block
----------------------------------------------------------------------------------------*/
long ZBlockFile::GetBlockRef( const long bkIndex )
{
Block b;
if ( bmap )
{
bmap->GetArrayItem( &b, bkIndex );
return b.fRefNum;
}
else
return -1;
}
/*------------------------------------*** CLOSE ***-----------------------------------*/
/*
write header and map, then close the file as normal
----------------------------------------------------------------------------------------*/
void ZBlockFile::Close()
{
WriteHeader();
WriteMap();
ZFile::Close();
ForgetObject( bmap );
bmap = NULL;
}
/*----------------------------------*** ADDBLOCK ***----------------------------------*/
/*
register a new block in the file's map. This allocates a ref num and returns it.
----------------------------------------------------------------------------------------*/
long ZBlockFile::AddBlock()
{
long aRef = GetNewRefSeed();
AddBlockUsingRef( aRef );
return aRef;
}
/*------------------------------*** ADDBLOCKUSINGREF ***------------------------------*/
/*
register a new block using the ref passed. Caller MUST ensure this is unique.
----------------------------------------------------------------------------------------*/
void ZBlockFile::AddBlockUsingRef( const long aRef )
{
if ( GetBlock( aRef ) == 0 )
{
useCount++;
// initially we set the size to zero, since we don't know until the data is written
// how big it is. This is fine, since a) we don't want to expand the file until we
// have to, and b) SetBlockData automatically manages the reallocation of blocks as
// needed to satisfy the size request.
AppendBlock( aRef, 0 );
}
else
FailOSErr( kBlockDupRefErr );
}
/*--------------------------------*** REMOVEBLOCK ***---------------------------------*/
/*
unregister block from file, returning the space it occupies as a free block.
----------------------------------------------------------------------------------------*/
void ZBlockFile::RemoveBlock( const long ref )
{
long i;
if ( i = GetBlock( ref ) > 0 )
{
useCount--;
FreeBlock( i );
CompactMap();
}
else
FailOSErr( kBlockNotFoundErr );
}
/*--------------------------------*** GETBLOCKDATA ***--------------------------------*/
/*
get the data of a block into an arbitrary buffer.
----------------------------------------------------------------------------------------*/
void ZBlockFile::GetBlockData( const long ref, void* dataPtr, long* dataLen )
{
long i = GetBlock( ref );
long iSize;
if ( i > 0 )
{
iSize = GetBlockSize( i );
SetMarkToBlock( i );
iSize = MIN( iSize, *dataLen );
Read((Ptr) dataPtr, &iSize );
*dataLen = iSize;
}
else
FailOSErr( kBlockNotFoundErr );
}
/*--------------------------------*** GETBLOCKDATA ***--------------------------------*/
/*
get the data of a blovk into an existing handle, replacing its contents and resizing as
necessary
----------------------------------------------------------------------------------------*/
void ZBlockFile::GetBlockData( const long ref, Handle dataH )
{
char hs;
long iSize, i;
FailNILParam( dataH );
i = GetBlock( ref );
if ( i > 0 )
{
hs = HGetState( dataH );
iSize = GetBlockSize( i );
SetHandleSize( dataH, iSize );
FailOSErr( MemError());
SetMarkToBlock( i );
HLock( dataH );
Read( *dataH, &iSize );
HSetState( dataH, hs );
}
else
FailOSErr( kBlockNotFoundErr );
}
/*--------------------------------*** SETBLOCKDATA ***--------------------------------*/
/*
put data from an arbitrary buffer into the file, associated with the block ref. This
splits or reallocates blocks as needed.
----------------------------------------------------------------------------------------*/
void ZBlockFile::SetBlockData( const long ref, void* dataPtr, long dataLen )
{
long iSize, i;
i = GetBlock( ref );
if ( i > 0 )
{
iSize = GetBlockSize( i );
// if the size of the block doesn't match <dataLen>, we may need to reallocate
// or split the block
if ( iSize != dataLen )
{
ReallocBlock( ref, dataLen );
i = GetBlock( ref );
if ( i <= 0 )
FailOSErr( kBlockBadRefErr );
iSize = GetBlockSize( i );
CompactMap();
}
// save the data to the block
SetMarkToBlock( i );
Write((Ptr) dataPtr, &iSize );
}
else
FailOSErr( kBlockNotFoundErr );
}
/*--------------------------------*** SETBLOCKDATA ***--------------------------------*/
/*
put data from a handle into the file associated with the block ref.
----------------------------------------------------------------------------------------*/
void ZBlockFile::SetBlockData( const long ref, Handle dataH )
{
char hs;
hs = HGetState( dataH );
HLock( dataH );
try
{
SetBlockData( ref, *dataH, GetHandleSize( dataH ));
}
catch( OSErr err )
{
HSetState( dataH, hs );
throw err;
}
HSetState( dataH, hs );
}
/*--------------------------------*** APPENDBLOCK ***---------------------------------*/
/*
assign a new block to contain the bytes requested. This initially tries to find a free
block big enough, splitting it if necessary, otherwise it appends a block to the end of
the file, growing it.
----------------------------------------------------------------------------------------*/
void ZBlockFile::AppendBlock( const long ref, const unsigned long sizeNeeded )
{
Block b;
long i;
i = FindFreeBlock( sizeNeeded );
if ( i > 0 )
{
// we found a block big enough to contain the data, so let's see what's in it:
bmap->GetArrayItem( &b, i );
// need to split it?
if ( sizeNeeded < b.fSize )
SplitBlock( i, sizeNeeded );
// set up entry- the mark & length fields are already set, so don't touch it:
b.fRefNum = ref;
b.fStatus = notFree;
bmap->SetArrayItem( &b, i );
}
else
{
// no block was found that can contain the size requested, so we'll have to append
// a new block to the end of the file.
b.fRefNum = ref;
b.fStatus = notFree;
b.fMark = GetLength();
b.fSize = sizeNeeded;
// grow the file to the desired length
SetLength( b.fMark + sizeNeeded );
// add item to map
bmap->AppendItem( &b );
}
}
/*-------------------------------*** FINDFREEBLOCK ***--------------------------------*/
/*
returns the index of a free block large enough to contain the bytes passed, or 0 if none
could be found.
----------------------------------------------------------------------------------------*/
long ZBlockFile::FindFreeBlock( const unsigned long sizeNeeded )
{
// locate a free block that can contain <sizeNeeded>.
long i;
Block b;
Boolean found = FALSE;
for ( i = 1; i <= bmap->CountItems(); i++ )
{
bmap->GetArrayItem( &b, i );
if (( b.fSize <= sizeNeeded ) && ( b.fStatus == free ))
{
found = TRUE;
break;
}
}
if ( found )
return i;
else
return 0;
}
/*----------------------------------*** GETBLOCK ***----------------------------------*/
/*
return the map index of a block with the given ref. This is a simple search through the
map.
----------------------------------------------------------------------------------------*/
long ZBlockFile::GetBlock( const long ref )
{
// locate the block with the given ref
long i;
Block b;
Boolean found = FALSE;
for ( i = 1; i <= bmap->CountItems(); i++ )
{
bmap->GetArrayItem( &b, i );
if ( b.fRefNum == ref )
{
found = TRUE;
break;
}
}
if ( found )
return i;
else
return 0;
}
/*---------------------------------*** FREEBLOCK ***----------------------------------*/
/*
mark the block with the index passed as available for re-use.
----------------------------------------------------------------------------------------*/
void ZBlockFile::FreeBlock( const long bkIndex )
{
// mark the block with the index passed as free.
Block b;
bmap->GetArrayItem( &b, bkIndex );
b.fRefNum = 0;
b.fStatus = free;
// if the block size is zero, it may as well be deleted from the map, since
// it is only cluttering it up and is not managing any space in the file.
if ( b.fSize == 0 )
bmap->DeleteItem( bkIndex );
else
bmap->SetArrayItem( &b, bkIndex );
}
/*---------------------------------*** SPLITBLOCK ***---------------------------------*/
/*
divide a block into a free part and a used part. This is done whenever a block becomes
smaller.
----------------------------------------------------------------------------------------*/
void ZBlockFile::SplitBlock( const long bkIndex, const unsigned long bkSize )
{
// splits a block in two, returning unused space to the pool.
Block b, subBlock;
bmap->GetArrayItem( &b, bkIndex );
// the sub-block will manage the bytes left over once <bkSize> has been
// alloted to this block.
subBlock.fRefNum = 0;
subBlock.fMark = b.fMark + bkSize;
subBlock.fSize = b.fSize - bkSize;
subBlock.fStatus = free;
// sanity check- subBlock can't be 0 or negative:
FailOSErr(( subBlock.fSize <= 0 )? kBadSplitReqErr : noErr );
// now change block to exact size
b.fSize = bkSize;
// add and update map items
bmap->AppendItem( &subBlock );
bmap->SetArrayItem( &b, bkIndex );
}
/*--------------------------------*** REALLOCBLOCK ***--------------------------------*/
/*
reassign the block <ref> to a new physical place in the file. This is done whenever the
data size changes, and it attempts to reuse space where possible.
----------------------------------------------------------------------------------------*/
void ZBlockFile::ReallocBlock( const long ref, long newBkSize )
{
// reassign the data to another block, since it's size has changed.
long i;
Block b;
i = GetBlock( ref );
if ( i > 0 )
{
bmap->GetArrayItem( &b, i );
// if new size is LESS than block, we can simply split it, returning the excess to
// the free space.
if ( newBkSize < b.fSize )
SplitBlock( i, newBkSize );
else
{
// new data is LARGER than before, so we free the whole block and allocate a
// new one.
FreeBlock( i );
AppendBlock( ref, newBkSize );
}
}
else
FailOSErr( kBlockNotFoundErr );
}
/*--------------------------------*** GETBLOCKSIZE ***--------------------------------*/
/*
return the size of the indexed block
----------------------------------------------------------------------------------------*/
unsigned long ZBlockFile::GetBlockSize( const long bkIndex )
{
Block b;
bmap->GetArrayItem( &b, bkIndex );
return b.fSize;
}
/*-------------------------------*** SETMARKTOBLOCK ***-------------------------------*/
/*
position the filemark at the place alloted by the indexed block.
----------------------------------------------------------------------------------------*/
void ZBlockFile::SetMarkToBlock( const long bkIndex )
{
Block b;
bmap->GetArrayItem( &b, bkIndex );
SetMark( b.fMark );
}
/*-----------------------------------*** INITMAP ***----------------------------------*/
/*
initialise the block map (creates it anew)
----------------------------------------------------------------------------------------*/
void ZBlockFile::InitMap()
{
FailNIL( bmap = new ZArray( sizeof( Block )));
}
/*--------------------------------*** COMPACTMAP ***----------------------------------*/
/*
conjoin adjacent free blocks into larger free blocks, thus increasing their chances of
being reused. This is a moderately efficient operation in that it does not change anything
in the file itself, but simply modifies the map.
----------------------------------------------------------------------------------------*/
void ZBlockFile::CompactMap()
{
// TO DO
}